package cool.webstudy.admin.service.impl.docker;

import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.CreateContainerResponse;
import com.github.dockerjava.api.command.ExecCreateCmdResponse;
import com.github.dockerjava.api.command.ExecStartCmd;
import com.github.dockerjava.api.model.ExposedPort;
import com.github.dockerjava.api.model.Frame;
import com.github.dockerjava.api.model.HostConfig;
import com.github.dockerjava.api.model.Ports;
import com.github.dockerjava.core.command.ExecStartResultCallback;
import cool.webstudy.admin.constant.enums.CompetitionStatusEnum;
import cool.webstudy.admin.constant.enums.rescode.docker.ContainerResCodeEnum;
import cool.webstudy.admin.constant.enums.rescode.docker.NetworkResCodeEnum;
import cool.webstudy.admin.exception.BusinessException;
import cool.webstudy.admin.model.dto.docker.container.ContainerIdAndNameMapInfoDTO;
import cool.webstudy.admin.model.dto.docker.container.ContainerNetworkInfoDTO;
import cool.webstudy.admin.service.callback.CallBack;
import cool.webstudy.admin.service.docker.ContainerService;
import cool.webstudy.admin.service.docker.NetworkService;
import cool.webstudy.admin.utils.IPUtil;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.FileInputStream;
import java.util.*;

/**
 * @author 莫振双
 * @date 2025/1/1 20:56
 * @description: 容器服务实现类
 */
@Service
public class ContainerServiceImpl implements ContainerService {
    @Autowired
    private DockerClient dockerClient;
    @Autowired
    private NetworkService networkService;

    @Override
    public String createContainer(String imageInfo, String containerName, String networkName, Integer hostPort, Integer containerPort) {
        //设置端口映射
        ExposedPort exposedPort = ExposedPort.tcp(containerPort);
        Ports portsBindings = new Ports();
        portsBindings.bind(exposedPort, Ports.Binding.bindPort(hostPort));
        HostConfig hostConfig = HostConfig.newHostConfig()
                .withPortBindings(portsBindings);
        //创建容器
        try {
            CreateContainerResponse response = dockerClient.createContainerCmd(imageInfo)
                    .withName(containerName)
                    .withHostConfig(hostConfig)
                    .withNetworkMode(networkName)
                    .exec();
            return response.getId();
        } catch (Exception e) {
            throw new BusinessException(ContainerResCodeEnum.RC_ERROR_CREATE, e.getMessage());
        }
    }

    @Override
    public String createContainer(String imageInfo, String containerName, String networkName, String ip) {
        try {
            CreateContainerResponse response = dockerClient.createContainerCmd(imageInfo)
                    .withName(containerName)
                    .withIpv4Address(ip)
                    .withNetworkMode(networkName)
                    .exec();
            return response.getId();
        } catch (Exception e) {
            throw new BusinessException(ContainerResCodeEnum.RC_ERROR_CREATE, e.getMessage());
        }
    }

    @Override
    public List<ContainerIdAndNameMapInfoDTO> batchCreateContainer(String imageInfo, String[] containerNames, Integer startHostPort, Integer endHostPort, Integer containerPort) {
        //入参校验
        //截止端口号不能小于起始端口号
        if (endHostPort < startHostPort) {
            throw new BusinessException(ContainerResCodeEnum.RC_START_PORT_NUM_NOT_LESS_THAN_END_PORT_NUM);
        }
        //查询网络信息
        String networkName = networkService.queryNetworkName();
        if (StringUtils.isBlank(networkName)) {
            throw new BusinessException(NetworkResCodeEnum.RC_NETWORK_INFO_MISSING);
        }
        List<ContainerIdAndNameMapInfoDTO> mappingInfoList = new ArrayList<>();
        for (int i = startHostPort, k = 0; i <= endHostPort; i++, k++) {
            String containerName = containerNames[k];
            String containerId = createContainer(imageInfo, containerName, networkName, i, containerPort);
            ContainerIdAndNameMapInfoDTO mappingInfo = new ContainerIdAndNameMapInfoDTO();
            mappingInfo.setId(containerId);
            mappingInfo.setName(containerName);
            mappingInfoList.add(mappingInfo);
        }
        return mappingInfoList;
    }

    @Override
    public Map<String, ContainerNetworkInfoDTO> batchCreateContainer(String imageInfo, String[] containerNames, String startIP, String endIP) {
        //查询网络信息
        String networkName = networkService.queryNetworkName();
        if (StringUtils.isBlank(networkName)) {
            throw new BusinessException(NetworkResCodeEnum.RC_NETWORK_INFO_MISSING);
        }
        //根据起始ip和截止ip生成连续的ip地址并放入list中
        List<String> ipList = IPUtil.generateIpList(startIP, endIP, true);
        if (ipList.isEmpty()) {
            throw new BusinessException(ContainerResCodeEnum.RC_ERROR_IP_LIST_IS_EMPTY);
        }
        Map<String, ContainerNetworkInfoDTO> mappingInfo = new HashMap<>();
        for (int i = 0; i < ipList.size(); i++) {
            String containerName = containerNames[i];
            String containerId = createContainer(imageInfo, containerName, networkName, ipList.get(i));
            ContainerNetworkInfoDTO networkInfo = new ContainerNetworkInfoDTO();
            networkInfo.setId(containerId);
            networkInfo.setName(containerName);
            networkInfo.setNetAddress(ipList.get(i));
            //设置默认的ssh连接端口
            networkInfo.setNetPort("22");
            mappingInfo.put(containerName, networkInfo);
        }
        return mappingInfo;
    }

    @Override
    public String[] batchStartContainer(String[] containerIds) {
        if (ArrayUtils.isEmpty(containerIds)) {
            return new String[0];
        }
        List<String> successContainerIds = new ArrayList<>();
        for (int i = 0; i < containerIds.length; i++) {
            try {
                dockerClient.startContainerCmd(containerIds[i]).exec().wait();
                successContainerIds.add(containerIds[i]);
            } catch (Exception e) {
                //TODO 添加日志
            }
        }
        return successContainerIds.toArray(new String[0]);
    }

    @Override
    public String[] batchStopContainer(String[] containerIds) {
        if (ArrayUtils.isEmpty(containerIds)) {
            return new String[0];
        }
        List<String> successContainerIds = new ArrayList<>();
        for (int i = 0; i < containerIds.length; i++) {
            try {
                dockerClient.stopContainerCmd(containerIds[i]).exec();
                successContainerIds.add(containerIds[i]);
            } catch (Exception e) {
                throw new BusinessException(ContainerResCodeEnum.RC_ERROR_STOP,e.getMessage());
            }
        }
        return successContainerIds.toArray(new String[0]);
    }

    @Override
    public String[] batchDeleteContainer(String[] containerIds) {
        if (ArrayUtils.isEmpty(containerIds)) {
            return new String[0];
        }
        List<String> successContainerIds = new ArrayList<>();
        for (int i = 0; i < containerIds.length; i++) {
            try {
                dockerClient.removeContainerCmd(containerIds[i]).exec();
                successContainerIds.add(containerIds[i]);
            } catch (Exception e) {
                throw new BusinessException(ContainerResCodeEnum.RC_ERROR_DELETE,e.getMessage());
            }
        }
        return successContainerIds.toArray(new String[0]);
    }

    @Override
    public String[] batchRestartContainer(String[] containerIds) {
        if (ArrayUtils.isEmpty(containerIds)) {
            return new String[0];
        }
        List<String> successContainerIds = new ArrayList<>();
        for (int i = 0; i < containerIds.length; i++) {
            try {
                dockerClient.restartContainerCmd(containerIds[i]).exec();
                successContainerIds.add(containerIds[i]);
            } catch (Exception e) {
                //TODO 添加日志
            }
        }
        return successContainerIds.toArray(new String[0]);
    }

    @Override
    public Boolean batchCopyFileToContainer(String[] containerIds, String srcFilePath, String destFilePath) {
        if (ArrayUtils.isEmpty(containerIds) || StringUtils.isBlank(srcFilePath) || StringUtils.isBlank(destFilePath)) {
            return false;
        }
        //读取文件
        File srcFile = new File(srcFilePath);
        if (!srcFile.exists()) {
            //文件不存在
            return false;
        }
        for (String containerId : containerIds) {
            try {
                dockerClient.copyArchiveToContainerCmd(containerId)
                        .withRemotePath(destFilePath)
                        // 该方法会将压缩包直接解压到容器中
                        .withTarInputStream(new FileInputStream(srcFile))
                        .exec().wait();
            } catch (Exception e) {
                //TODO 添加日志
            }
        }
        return true;
    }

    @Override
    public void batchExecCommandInContainer(String[] containerIds, String command, CallBack callback) {
        for (String containerId : containerIds) {
            execCommandInContainer(containerId, command, callback);
        }
    }

    @Override
    public void execCommandInContainer(String containerId, String command, CallBack callback) {
        if (StringUtils.isBlank(containerId) || StringUtils.isBlank(command)) {
            return;
        }
        // 定义一个包含多条命令的字符串
        final String[] commands = {"sh", "-c", command};
        try {
            //创建命令执行实例
            ExecCreateCmdResponse createCmdResponse = dockerClient.execCreateCmd(containerId)
                    .withAttachStdout(true)
                    .withAttachStderr(true)
                    .withCmd(commands)
                    .withUser("root")
                    .exec();

            // 执行命令并捕获输出
            ExecStartCmd execStartCmd = dockerClient.execStartCmd(createCmdResponse.getId());
            StringBuilder errorOutput = new StringBuilder();
            StringBuilder output = new StringBuilder();
            execStartCmd.exec(new ExecStartResultCallback() {
                @Override
                public void onNext(Frame frame) {
                    switch (frame.getStreamType()) {
                        case STDERR:
                            errorOutput.append(new String(frame.getPayload()));
                            break;
                        case STDOUT:
                            output.append(new String(frame.getPayload()));
                            break;
                    }
                }

                @Override
                public void onError(Throwable throwable) {
                    //回调修改赛事状态的方法
                    if (Objects.nonNull(callback)){
                        callback.run(CompetitionStatusEnum.INITIALIZATION_FAILED);
                    }
                    //TODO 添加日志
                }

                @Override
                public void onComplete() {
                    //回调修改赛事状态的方法，callback方法发生的异常也会被上面的onError方法捕获，所以这里不需要再捕获异常
                    if (Objects.nonNull(callback)){
                        callback.run(CompetitionStatusEnum.READY_FOR_COMP);
                    }
                }
            });
        } catch (Exception e) {
            //TODO 添加日志
        }
    }

    @Override
    public void batchRefreshFlag(Map<String, String> params) {
        if (Objects.isNull(params) || params.isEmpty()){
            return;
        }
        for (Map.Entry<String, String> entry : params.entrySet()) {
            String containerId = entry.getKey();
            String flag = entry.getValue();
            String command = "./flag.sh " + flag;
            execCommandInContainer(containerId, command, null);
        }
    }
}
